1/**
2 * @file gridfinity-rebuilt-utility.scad
3 * @brief UTILITY FILE, DO NOT EDIT
4 * EDIT OTHER FILES IN REPO FOR RESULTS
5 */
6
7include <standard.scad>
8use <generic-helpers.scad>
9
10// ===== User Modules ===== //
11
12// functions to convert gridz values to mm values
13
14/**
15 * @Summary Convert a number from Gridfinity values to mm.
16 * @details Also can include lip when working with height values.
17 * @param gridfinityUnit Gridfinity is normally on a base 7 system.
18 * @param includeLipHeight Include the lip height as well.
19 * @returns The final value in mm.
20 */
21function fromGridfinityUnits(gridfinityUnit, includeLipHeight = false) =
22 gridfinityUnit*7 + (includeLipHeight ? h_lip : 0);
23
24/**
25 * @Summary Height in mm including fixed heights.
26 * @details Also can include lip when working with height values.
27 * @param mmHeight Height without other values.
28 * @param includeLipHeight Include the lip height as well.
29 * @returns The final value in mm.
30 */
31function includingFixedHeights(mmHeight, includeLipHeight = false) =
32 mmHeight + h_bot + h_base + (includeLipHeight ? h_lip : 0);
33
34/**
35 * @brief Three Functions in One. For height calculations.
36 * @param z Height value
37 * @param gridz_define As explained in gridfinity-rebuilt-bins.scad
38 * @param l style_lip as explained in gridfinity-rebuilt-bins.scad
39 * @returns Height in mm
40 */
41function hf (z, gridz_define, style_lip) =
42 gridz_define==0 ? fromGridfinityUnits(z, style_lip==2) :
43 gridz_define==1 ? includingFixedHeights(z, style_lip==2) :
44 z + ( // Just use z (possibly adding/subtracting lip)
45 style_lip==1 ? -h_lip :
46 style_lip==2 ? h_lip : 0
47 )
48 ;
49
50/**
51 * @brief Calculates the proper height for bins. Three Functions in One.
52 * @param z Height value
53 * @param d gridz_define as explained in gridfinity-rebuilt-bins.scad
54 * @param l style_lip as explained in gridfinity-rebuilt-bins.scad
55 * @param enable_zsnap Automatically snap the bin size to the nearest 7mm increment.
56 * @returns Height in mm
57 */
58function height (z,d=0,l=0,enable_zsnap=true) =
59 (
60 enable_zsnap ? (
61 (abs(hf(z,d,l))%7==0) ? hf(z,d,l) :
62 hf(z,d,l)+7-abs(hf(z,d,l))%7
63 )
64 :hf(z,d,l)
65 ) -h_base;
66
67// Creates equally divided cutters for the bin
68//
69// n_divx: number of x compartments (ideally, coprime w/ gridx)
70// n_divy: number of y compartments (ideally, coprime w/ gridy)
71// set n_div values to 0 for a solid bin
72// style_tab: tab style for all compartments. see cut()
73// scoop_weight: scoop toggle for all compartments. see cut()
74module cutEqual(n_divx=1, n_divy=1, style_tab=1, scoop_weight=1) {
75 for (i = [1:n_divx])
76 for (j = [1:n_divy])
77 cut((i-1)*$gxx/n_divx,(j-1)*$gyy/n_divy, $gxx/n_divx, $gyy/n_divy, style_tab, scoop_weight);
78}
79
80
81// Creates equally divided cylindrical cutouts
82//
83// n_divx: number of x cutouts
84// n_divy: number of y cutouts
85// set n_div values to 0 for a solid bin
86// cylinder_diameter: diameter of cutouts
87// cylinder_height: height of cutouts
88// coutout_depth: offset from top to solid part of container
89// orientation: orientation of cylinder cutouts (0 = x direction, 1 = y direction, 2 = z direction)
90// chamfer: chamfer around the top rim of the holes
91module cutCylinders(n_divx=1, n_divy=1, cylinder_diameter=1, cylinder_height=1, coutout_depth=0, orientation=0, chamfer=0.5) {
92 rotation = (orientation == 0)
93 ? [0,90,0]
94 : (orientation == 1)
95 ? [90,0,0]
96 : [0,0,0];
97
98 gridx_mm = $gxx*l_grid;
99 gridy_mm = $gyy*l_grid;
100 padding = 2;
101 cutout_x = gridx_mm - d_wall*2;
102 cutout_y = gridy_mm - d_wall*2;
103
104 cut_move(x=0, y=0, w=$gxx, h=$gyy) {
105 translate([0,0,-coutout_depth]) {
106 rounded_rectangle(cutout_x, cutout_y, coutout_depth*2, r_base);
107
108 pattern_linear(x=n_divx, y=n_divy, sx=(gridx_mm - padding)/n_divx, sy=(gridy_mm - padding)/n_divy)
109 rotate(rotation)
110 union() {
111 cylinder(d=cylinder_diameter, h=cylinder_height*2, center=true);
112 if (chamfer > 0) {
113 translate([0,0,-chamfer]) cylinder(d1=cylinder_diameter, d2=cylinder_diameter+4*chamfer, h=2*chamfer);
114 }
115 };
116 }
117 }
118}
119
120// initialize gridfinity
121// sl: lip style of this bin.
122// 0:Regular lip, 1:Remove lip subtractively, 2:Remove lip and retain height
123module gridfinityInit(gx, gy, h, h0 = 0, l = l_grid, sl = 0) {
124 $gxx = gx;
125 $gyy = gy;
126 $dh = h;
127 $dh0 = h0;
128 $style_lip = sl;
129 difference() {
130 color("firebrick")
131 block_bottom(h0==0?$dh-0.1:h0, gx, gy, l);
132 children();
133 }
134 color("royalblue")
135 block_wall(gx, gy, l) {
136 if ($style_lip == 0) profile_wall(h);
137 else profile_wall2(h);
138 }
139}
140// Function to include in the custom() module to individually slice bins
141// Will try to clamp values to fit inside the provided base size
142//
143// x: start coord. x=1 is the left side of the bin.
144// y: start coord. y=1 is the bottom side of the bin.
145// w: width of compartment, in # of bases covered
146// h: height of compartment, in # of basese covered
147// t: tab style of this specific compartment.
148// alignment only matters if the compartment size is larger than d_tabw
149// 0:full, 1:auto, 2:left, 3:center, 4:right, 5:none
150// Automatic alignment will use left tabs for bins on the left edge, right tabs for bins on the right edge, and center tabs everywhere else.
151// s: toggle the rounded back corner that allows for easy removal
152
153module cut(x=0, y=0, w=1, h=1, t=1, s=1, tab_width=d_tabw, tab_height=d_tabh) {
154 translate([0,0,-$dh-h_base])
155 cut_move(x,y,w,h)
156 block_cutter(clp(x,0,$gxx), clp(y,0,$gyy), clp(w,0,$gxx-x), clp(h,0,$gyy-y), t, s, tab_width, tab_height);
157}
158
159
160// cuts equally sized bins over a given length at a specified position
161// bins_x: number of bins along x-axis
162// bins_y: number of bins along y-axis
163// len_x: length (in gridfinity bases) along x-axis that the bins_x will fill
164// len_y: length (in gridfinity bases) along y-axis that the bins_y will fill
165// pos_x: start x position of the bins (left side)
166// pos_y: start y position of the bins (bottom side)
167// style_tab: Style of the tab used on the bins
168// scoop: Weight of the scoop on the bottom of the bins
169// tab_width: Width of the tab on the bins, in mm.
170// tab_height: How far the tab will stick out over the bin, in mm. Default tabs fit 12mm labels, but for narrow bins can take up too much space over the opening. This setting allows 'slimmer' tabs for use with thinner labels, so smaller/narrower bins can be labeled and still keep a reasonable opening at the top. NOTE: The measurement is not 1:1 in mm, so a '3.5' value does not guarantee a tab that fits 3.5mm label tape. Use the 'measure' tool after rendering to check the distance between faces to guarantee it fits your needs.
171module cutEqualBins(bins_x=1, bins_y=1, len_x=1, len_y=1, pos_x=0, pos_y=0, style_tab=5, scoop=1, tab_width=d_tabw, tab_height=d_tabh) {
172 // Calculate width and height of each bin based on total length and number of bins
173 bin_width = len_x / bins_x;
174 bin_height = len_y / bins_y;
175
176 // Loop through each bin position in x and y direction
177 for (i = [0:bins_x-1]) {
178 for (j = [0:bins_y-1]) {
179 // Calculate the starting position for each bin
180 // Adjust position by adding pos_x and pos_y to shift the entire grid of bins as needed
181 bin_start_x = pos_x + i * bin_width;
182 bin_start_y = pos_y + j * bin_height;
183
184 // Call the cut module to create each bin with calculated position and dimensions
185 // Pass through the style_tab and scoop parameters
186 cut(bin_start_x, bin_start_y, bin_width, bin_height, style_tab, scoop, tab_width=tab_width, tab_height=tab_height);
187 }
188 }
189}
190
191// Translates an object from the origin point to the center of the requested compartment block, can be used to add custom cuts in the bin
192// See cut() module for parameter descriptions
193module cut_move(x, y, w, h) {
194 translate([0,0,$dh0==0?$dh+h_base:$dh0+h_base])
195 cut_move_unsafe(clp(x,0,$gxx), clp(y,0,$gyy), clp(w,0,$gxx-x), clp(h,0,$gyy-y))
196 children();
197}
198
199// ===== Modules ===== //
200
201module profile_base() {
202 polygon([
203 [0,0],
204 [0,h_base],
205 [r_base,h_base],
206 [r_base-r_c2,h_base-r_c2],
207 [r_base-r_c2,r_c1],
208 [r_base-r_c2-r_c1,0]
209 ]);
210}
211
212module gridfinityBase(gx, gy, l, dx, dy, style_hole, off=0, final_cut=true, only_corners=false) {
213 dbnxt = [for (i=[1:5]) if (abs(gx*i)%1 < 0.001 || abs(gx*i)%1 > 0.999) i];
214 dbnyt = [for (i=[1:5]) if (abs(gy*i)%1 < 0.001 || abs(gy*i)%1 > 0.999) i];
215 dbnx = 1/(dx==0 ? len(dbnxt) > 0 ? dbnxt[0] : 1 : round(dx));
216 dbny = 1/(dy==0 ? len(dbnyt) > 0 ? dbnyt[0] : 1 : round(dy));
217 xx = gx*l-0.5;
218 yy = gy*l-0.5;
219
220 if (final_cut)
221 translate([0,0,h_base])
222 rounded_rectangle(xx+0.002, yy+0.002, h_bot/1.5, r_fo1+0.001);
223
224 intersection(){
225 if (final_cut)
226 translate([0,0,-1])
227 rounded_rectangle(xx+0.005, yy+0.005, h_base+h_bot/2*10, r_fo1+0.001);
228
229 if((style_hole != 0) && (only_corners)) {
230 difference(){
231 pattern_linear(gx/dbnx, gy/dbny, dbnx*l, dbny*l)
232 block_base(gx, gy, l, dbnx, dbny, 0, off);
233 if (style_hole == 4) {
234 translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
235 refined_hole();
236 mirror([1, 0, 0])
237 translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
238 refined_hole();
239 mirror([0, 1, 0]) {
240 translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
241 refined_hole();
242 mirror([1, 0, 0])
243 translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
244 refined_hole();
245 }
246 }
247 else {
248 pattern_linear(2, 2, (gx-1)*l_grid+d_hole, (gy-1)*l_grid+d_hole)
249 block_base_hole(style_hole, off);
250 }
251 }
252 }
253 else {
254 pattern_linear(gx/dbnx, gy/dbny, dbnx*l, dbny*l)
255 block_base(gx, gy, l, dbnx, dbny, style_hole, off);
256 }
257 }
258}
259
260/**
261 * @brief A single Gridfinity base.
262 * @param gx
263 * @param gy
264 * @param l
265 * @param dbnx
266 * @param dbny
267 * @param style_hole
268 * @param off
269 */
270module block_base(gx, gy, l, dbnx, dbny, style_hole, off) {
271 render(convexity = 2)
272 difference() {
273 block_base_solid(dbnx, dbny, l, off);
274
275 if (style_hole > 0)
276 pattern_circular(abs(l-d_hole_from_side/2)<0.001?1:4)
277 if (style_hole == 4)
278 translate([l/2-d_hole_from_side, l/2-d_hole_from_side, h_slit*2])
279 refined_hole();
280 else
281 translate([l/2-d_hole_from_side, l/2-d_hole_from_side, 0])
282 block_base_hole(style_hole, off);
283 }
284}
285
286/**
287 * @brief A gridfinity base with no holes.
288 * @details Used as the "base" with holes removed from it later.
289 * @param dbnx
290 * @param dbny
291 * @param l
292 * @param o
293 */
294module block_base_solid(dbnx, dbny, l, o) {
295 xx = dbnx*l-0.05;
296 yy = dbny*l-0.05;
297 oo = (o/2)*(sqrt(2)-1);
298 translate([0,0,h_base])
299 mirror([0,0,1])
300 union() {
301 hull() {
302 rounded_rectangle(xx-2*r_c2-2*r_c1+o, yy-2*r_c2-2*r_c1+o, h_base+oo, r_fo3);
303 rounded_rectangle(xx-2*r_c2+o, yy-2*r_c2+o, h_base-r_c1+oo, r_fo2);
304 }
305 translate([0,0,oo])
306 hull() {
307 rounded_rectangle(xx-2*r_c2+o, yy-2*r_c2+o, r_c2, r_fo2);
308 mirror([0,0,1])
309 rounded_rectangle(xx+o, yy+o, h_bot/2+abs(10*o), r_fo1);
310 }
311 }
312}
313
314module block_base_hole(style_hole, o=0) {
315 r1 = r_hole1-o/2;
316 r2 = r_hole2-o/2;
317 union() {
318 difference() {
319 cylinder(h = 2*(h_hole-o+(style_hole==3?h_slit:0)), r=r2, center=true);
320
321 if (style_hole==3)
322 copy_mirror([0,1,0])
323 translate([-1.5*r2,r1+0.1,h_hole-o])
324 cube([r2*3,r2*3, 10]);
325 }
326 if (style_hole > 1)
327 cylinder(h = 2*h_base-o, r = r1, center=true);
328 }
329}
330
331
332module refined_hole() {
333 /**
334 * Refined hole based on Printables @grizzie17's Gridfinity Refined
335 * https://www.printables.com/model/413761-gridfinity-refined
336 */
337
338 // Meassured magnet hole diameter to be 5.86mm (meassured in fusion360
339 r = r_hole2-0.32;
340
341 // Magnet height
342 m = 2;
343 mh = m-0.1;
344
345 // Poke through - For removing a magnet using a toothpick
346 ptl = h_slit*3; // Poke Through Layers
347 pth = mh+ptl; // Poke Through Height
348 ptr = 2.5; // Poke Through Radius
349
350 union() {
351 hull() {
352 // Magnet hole - smaller than the magnet to keep it squeezed
353 translate([10, -r, 0]) cube([1, r*2, mh]);
354 cylinder(1.9, r=r);
355 }
356 hull() {
357 // Poke hole
358 translate([-9+5.60, -ptr/2, -ptl]) cube([1, ptr, pth]);
359 translate([-12.53+5.60, 0, -ptl]) cylinder(pth, d=ptr);
360 }
361 }
362}
363
364/**
365 * @brief Stacking lip based on https://gridfinity.xyz/specification/
366 * @details Also includes a support base.
367 */
368module stacking_lip() {
369 // Technique: Descriptive constant names are useful, but can be unweildy.
370 // Use abbreviations if they are going to be re-used repeatedly in a small piece of code.
371 inner_slope = stacking_lip_inner_slope_height_mm;
372 wall_height = stacking_lip_wall_height_mm;
373
374 support_wall = stacking_lip_support_wall_height_mm;
375 s_total = stacking_lip_support_height_mm;
376
377 polygon([
378 [0, 0], // Inner tip
379 [inner_slope, inner_slope], // Go out 45 degrees
380 [inner_slope, inner_slope+wall_height], // Vertical increase
381 [stacking_lip_depth, stacking_lip_height], // Go out 45 degrees
382 [stacking_lip_depth, -s_total], // Down to support bottom
383 [0, -support_wall], // Up and in
384 [0, 0] // Close the shape. Tehcnically not needed.
385 ]);
386}
387
388/**
389 * @brief Stacking lip with a with a chamfered (rounded) top.
390 * @details Based on https://gridfinity.xyz/specification/
391 * Also includes a support base.
392 */
393module stacking_lip_chamfered() {
394 radius_center_y = h_lip - r_f1;
395
396 union() {
397 // Create rounded top
398 intersection() {
399 translate([0, radius_center_y, 0])
400 square([stacking_lip_depth, stacking_lip_height]);
401 offset(r = r_f1)
402 offset(delta = -r_f1)
403 stacking_lip();
404 }
405 // Remove pointed top
406 difference(){
407 stacking_lip();
408 translate([0, radius_center_y, 0])
409 square([stacking_lip_depth*2, stacking_lip_height*2]);
410 }
411 }
412}
413
414/**
415 * @brief External wall profile, with a stacking lip.
416 * @details Translated so a 90 degree rotation produces the expected outside radius.
417 */
418module profile_wall(height_mm) {
419 assert(is_num(height_mm))
420 translate([r_base - stacking_lip_depth, 0, 0]){
421 translate([0, height_mm, 0])
422 stacking_lip_chamfered();
423 translate([stacking_lip_depth-d_wall/2, 0, 0])
424 square([d_wall/2, height_mm]);
425 }
426}
427
428// lipless profile
429module profile_wall2(height_mm) {
430 assert(is_num(height_mm))
431 translate([r_base,0,0])
432 mirror([1,0,0])
433 square([d_wall, height_mm]);
434}
435
436module block_wall(gx, gy, l) {
437 translate([0,0,h_base])
438 sweep_rounded(gx*l-2*r_base-0.5-0.001, gy*l-2*r_base-0.5-0.001)
439 children();
440}
441
442module block_bottom( h = 2.2, gx, gy, l ) {
443 translate([0,0,h_base+0.1])
444 rounded_rectangle(gx*l-0.5-d_wall/4, gy*l-0.5-d_wall/4, h, r_base+0.01);
445}
446
447module cut_move_unsafe(x, y, w, h) {
448 xx = ($gxx*l_grid+d_magic);
449 yy = ($gyy*l_grid+d_magic);
450 translate([(x)*xx/$gxx,(y)*yy/$gyy,0])
451 translate([(-xx+d_div)/2,(-yy+d_div)/2,0])
452 translate([(w*xx/$gxx-d_div)/2,(h*yy/$gyy-d_div)/2,0])
453 children();
454}
455
456module block_cutter(x,y,w,h,t,s,tab_width=d_tabw,tab_height=d_tabh) {
457
458 v_len_tab = tab_height;
459 v_len_lip = d_wall2-d_wall+1.2;
460 v_cut_tab = tab_height - (2*r_f1)/tan(a_tab);
461 v_cut_lip = d_wall2-d_wall-d_clear;
462 v_ang_tab = a_tab;
463 v_ang_lip = 45;
464
465 ycutfirst = y == 0 && $style_lip == 0;
466 ycutlast = abs(y+h-$gyy)<0.001 && $style_lip == 0;
467 xcutfirst = x == 0 && $style_lip == 0;
468 xcutlast = abs(x+w-$gxx)<0.001 && $style_lip == 0;
469 zsmall = ($dh+h_base)/7 < 3;
470
471 ylen = h*($gyy*l_grid+d_magic)/$gyy-d_div;
472 xlen = w*($gxx*l_grid+d_magic)/$gxx-d_div;
473
474 height = $dh;
475 extent = (abs(s) > 0 && ycutfirst ? d_wall2-d_wall-d_clear : 0);
476 tab = (zsmall || t == 5) ? (ycutlast?v_len_lip:0) : v_len_tab;
477 ang = (zsmall || t == 5) ? (ycutlast?v_ang_lip:0) : v_ang_tab;
478 cut = (zsmall || t == 5) ? (ycutlast?v_cut_lip:0) : v_cut_tab;
479 style = (t > 1 && t < 5) ? t-3 : (x == 0 ? -1 : xcutlast ? 1 : 0);
480
481 translate([0,ylen/2,h_base+h_bot])
482 rotate([90,0,-90]) {
483
484 if (!zsmall && xlen - tab_width > 4*r_f2 && (t != 0 && t != 5)) {
485 fillet_cutter(3,"bisque")
486 difference() {
487 transform_tab(style, xlen, ((xcutfirst&&style==-1)||(xcutlast&&style==1))?v_cut_lip:0, tab_width)
488 translate([ycutlast?v_cut_lip:0,0])
489 profile_cutter(height-h_bot, ylen/2, s);
490
491 if (xcutfirst)
492 translate([0,0,(xlen/2-r_f2)-v_cut_lip])
493 cube([ylen,height,v_cut_lip*2]);
494
495 if (xcutlast)
496 translate([0,0,-(xlen/2-r_f2)-v_cut_lip])
497 cube([ylen,height,v_cut_lip*2]);
498 }
499 if (t != 0 && t != 5)
500 fillet_cutter(2,"indigo")
501 difference() {
502 transform_tab(style, xlen, ((xcutfirst&&style==-1)||(xcutlast&&style==1)?v_cut_lip:0), tab_width)
503 difference() {
504 intersection() {
505 profile_cutter(height-h_bot, ylen-extent, s);
506 profile_cutter_tab(height-h_bot, v_len_tab, v_ang_tab);
507 }
508 if (ycutlast) profile_cutter_tab(height-h_bot, v_len_lip, 45);
509 }
510
511 if (xcutfirst)
512 translate([ylen/2,0,xlen/2])
513 rotate([0,90,0])
514 transform_main(2*ylen)
515 profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
516
517 if (xcutlast)
518 translate([ylen/2,0,-xlen/2])
519 rotate([0,-90,0])
520 transform_main(2*ylen)
521 profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
522 }
523 }
524
525 fillet_cutter(1,"seagreen")
526 translate([0,0,xcutlast?v_cut_lip/2:0])
527 translate([0,0,xcutfirst?-v_cut_lip/2:0])
528 transform_main(xlen-(xcutfirst?v_cut_lip:0)-(xcutlast?v_cut_lip:0))
529 translate([cut,0])
530 profile_cutter(height-h_bot, ylen-extent-cut-(!s&&ycutfirst?v_cut_lip:0), s);
531
532 fillet_cutter(0,"hotpink")
533 difference() {
534 transform_main(xlen)
535 difference() {
536 profile_cutter(height-h_bot, ylen-extent, s);
537
538 if (!((zsmall || t == 5) && !ycutlast))
539 profile_cutter_tab(height-h_bot, tab, ang);
540
541 if (!(abs(s) > 0)&& y == 0)
542 translate([ylen-extent,0,0])
543 mirror([1,0,0])
544 profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
545 }
546
547 if (xcutfirst)
548 color("indigo")
549 translate([ylen/2+0.001,0,xlen/2+0.001])
550 rotate([0,90,0])
551 transform_main(2*ylen)
552 profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
553
554 if (xcutlast)
555 color("indigo")
556 translate([ylen/2+0.001,0,-xlen/2+0.001])
557 rotate([0,-90,0])
558 transform_main(2*ylen)
559 profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
560 }
561
562 }
563}
564
565module transform_main(xlen) {
566 translate([0,0,-(xlen-2*r_f2)/2])
567 linear_extrude(xlen-2*r_f2)
568 children();
569}
570
571module transform_tab(type, xlen, cut, tab_width=d_tabw) {
572 mirror([0,0,type==1?1:0])
573 copy_mirror([0,0,-(abs(type)-1)])
574 translate([0,0,-(xlen)/2])
575 translate([0,0,r_f2])
576 linear_extrude((xlen-tab_width-abs(cut))/(1-(abs(type)-1))-2*r_f2)
577 children();
578}
579
580module fillet_cutter(t = 0, c = "goldenrod") {
581 color(c)
582 minkowski() {
583 children();
584 sphere(r = r_f2-t/1000);
585 }
586}
587
588module profile_cutter(h, l, s) {
589 scoop = max(s*$dh/2-r_f2,0);
590 translate([r_f2,r_f2])
591 hull() {
592 if (l-scoop-2*r_f2 > 0)
593 square(0.1);
594 if (scoop < h) {
595 translate([l-2*r_f2,h-r_f2/2])
596 mirror([1,1])
597 square(0.1);
598
599 translate([0,h-r_f2/2])
600 mirror([0,1])
601 square(0.1);
602 }
603 difference() {
604 translate([l-scoop-2*r_f2, scoop])
605 if (scoop != 0) {
606 intersection() {
607 circle(scoop);
608 mirror([0,1]) square(2*scoop);
609 }
610 } else mirror([1,0]) square(0.1);
611 translate([l-scoop-2*r_f2,-1])
612 square([-(l-scoop-2*r_f2),2*h]);
613
614 translate([0,h])
615 square([2*l,scoop]);
616 }
617 }
618}
619
620module profile_cutter_tab(h, tab, ang) {
621 if (tab > 0)
622 color("blue")
623 offset(delta = r_f2)
624 polygon([[0,h],[tab,h],[0,h-tab*tan(ang)]]);
625
626}